﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using MvdDonk.Bomberman.Menu;
using Microsoft.Xna.Framework.Input;

namespace MvdDonk.Bomberman.Screens.Base
{
    public enum MenuOrientation
    {
        Vertical,
        Horizontal
    }

    /// <summary>
    /// Base class for screens that contain a menu of options. The user can
    /// move up and down to select an entry, or cancel to back out of the screen.
    /// </summary>
    public abstract class MenuScreen : GameScreen
    {
        public MenuOrientation Orientation { get; private set; }

        Texture2D gradientTexture;
        int selectedEntry = 0;
        string menuTitle;

        #region Key down repeat limit
        //TODO This should be handled in the InputManager
        private Keys lastKey = Keys.None;
        private double keyRepeatInitialDelay = 500;
        private double keyRepeatRate = 50;
        private double keyDownElapsedTime = 0;
        private double keyDownElapsedTimeInitial = 0;
        #endregion

        /// <summary>
        /// Gets the list of menu entries, so derived classes can add
        /// or change the menu contents.
        /// </summary>
        protected List<MenuEntry> MenuEntries { get; private set; }


        public MenuScreen(string menuTitle, MenuOrientation orientation)
        {
            this.menuTitle = menuTitle;
            MenuEntries = new List<MenuEntry>();
            TransitionOnTime = TimeSpan.FromSeconds(0.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
            Orientation = orientation;
        }

        #region Handle Input


        /// <summary>
        /// Responds to user input, changing the selected entry and accepting
        /// or cancelling the menu.
        /// </summary>
        public override void HandleInput(GameTime gameTime)
        {
            if (lastKey != Keys.None && Bomberman.Instance.InputManager.KeyDown(lastKey))
            {
                keyDownElapsedTime += gameTime.ElapsedGameTime.TotalMilliseconds;
                keyDownElapsedTimeInitial += gameTime.ElapsedGameTime.TotalMilliseconds;
            }

            var numberSelectableMenuEntries = MenuEntries.Where(m => m.Selectable).Count();

            if (numberSelectableMenuEntries > 0)
            {
                // Move to the previous menu entry?
                if (((Bomberman.Instance.InputManager.KeyDown(Keys.Up) && Orientation == MenuOrientation.Vertical) ||
                    (Bomberman.Instance.InputManager.KeyDown(Keys.Left) && Orientation == MenuOrientation.Horizontal))
                    && RepeatKeyAllowedOrNA())
                {
                    FindSelectableEntry(false);

                    StartKeyDownLimiter(Orientation == MenuOrientation.Vertical ? Keys.Up : Keys.Left);
                }

                // Move to the next menu entry?
                if (((Bomberman.Instance.InputManager.KeyDown(Keys.Down) && Orientation == MenuOrientation.Vertical) ||
                    (Bomberman.Instance.InputManager.KeyDown(Keys.Right) && Orientation == MenuOrientation.Horizontal))
                    && RepeatKeyAllowedOrNA())
                {
                    FindSelectableEntry(true);
                 
                    StartKeyDownLimiter(Orientation == MenuOrientation.Vertical ? Keys.Down : Keys.Right);
                }
            }

            if (Bomberman.Instance.InputManager.KeyClicked(Keys.Enter) || Bomberman.Instance.InputManager.KeyClicked(Keys.Space))
            {
                OnSelectEntry(selectedEntry);
            }
            else if (Bomberman.Instance.InputManager.KeyClicked(Keys.Escape))
            {
                OnCancel();
            }

            if (!Bomberman.Instance.InputManager.KeyDown(lastKey))
                lastKey = Keys.None;
        }

        private void FindSelectableEntry(bool findNext)
        {
            selectedEntry += findNext ? 1 : -1;
            if (findNext)
            {
                if (selectedEntry >= MenuEntries.Count)
                    selectedEntry = 0;
            }
            else
            {
                if (selectedEntry < 0)
                    selectedEntry = MenuEntries.Count - 1;
            }
            while (!MenuEntries[selectedEntry].Selectable)
                FindSelectableEntry(findNext);
        }

        private void StartKeyDownLimiter(Keys activeKey)
        {
            if (lastKey != activeKey)
            {
                //New key pressed
                lastKey = activeKey;
                keyDownElapsedTimeInitial = 0;
            }
            else
                keyDownElapsedTime = 0;
        }

        private bool RepeatKeyAllowedOrNA()
        {
            if (!Bomberman.Instance.InputManager.KeyDown(lastKey))
                return true;
            else
            {
                if (keyDownElapsedTimeInitial > keyRepeatInitialDelay && keyDownElapsedTime > keyRepeatRate)
                    return true;
                else
                    return false;
            }
        }


        /// <summary>
        /// Handler for when the user has chosen a menu entry.
        /// </summary>
        protected virtual void OnSelectEntry(int entryIndex)
        {
            if (MenuEntries[entryIndex].Selectable)
                MenuEntries[entryIndex].OnSelectEntry();
        }


        /// <summary>
        /// Handler for when the user has cancelled the menu.
        /// </summary>
        protected virtual void OnCancel()
        {
            ExitScreen();
        }


        /// <summary>
        /// Helper overload makes it easy to use OnCancel as a MenuEntry event handler.
        /// </summary>
        protected void OnCancel(object sender, EventArgs e)
        {
            OnCancel();
        }

        #endregion
        public override void LoadContent()
        {
            base.LoadContent();
            gradientTexture = Bomberman.Instance.ResourceManager.RequestTexture("gradient");
        }

        #region Update and Draw


        /// <summary>
        /// Allows the screen the chance to position the menu entries. By default
        /// all menu entries are lined up in a vertical list, centered on the screen.
        /// </summary>
        protected virtual void UpdateMenuEntryLocations(Rectangle? viewport = null)
        {
            float defaultY = 175f;
            int viewportWidth = (viewport != null && viewport.HasValue) ? viewport.Value.Width : Bomberman.Instance.GraphicsDevice.Viewport.Width;
            int viewportHeight = (viewport != null && viewport.HasValue) ? viewport.Value.Height : Bomberman.Instance.GraphicsDevice.Viewport.Height;

            if (Orientation == MenuOrientation.Vertical)
            {
                // Make the menu slide into place during transitions, using a
                // power curve to make things look more interesting (this makes
                // the movement slow down as it nears the end).
                float transitionOffset = (float)Math.Pow(TransitionPosition, 2);

                // start at Y = 175; each X value is generated per entry
                Vector2 position = new Vector2(0f, ((viewport != null && viewport.HasValue) ? viewport.Value.Y : defaultY));

                // update each menu entry's location in turn
                foreach (var menuEntry in MenuEntries)
                {
                    // each entry is to be centered horizontally
                    position.X = viewportWidth / 2 - menuEntry.GetWidth(this) / 2;

                    if (ScreenState == ScreenState.TransitionOn)
                        position.X -= transitionOffset * 256;
                    else
                        position.X += transitionOffset * 512;

                    // set the entry's position
                    menuEntry.Position = position;

                    // move down for the next entry the size of this entry
                    position.Y += menuEntry.GetHeight(this);
                }
            }
            else if (Orientation == MenuOrientation.Horizontal)
            {
                // Make the menu slide into place during transitions, using a
                // power curve to make things look more interesting (this makes
                // the movement slow down as it nears the end).
                float transitionOffset = (float)Math.Pow(TransitionPosition, 2);


                int totalMenuEntriesWidth = 0;
                foreach (var menuEntry in MenuEntries)
                    totalMenuEntriesWidth += menuEntry.GetWidth(this);

                int menuEntryPadding = (viewportWidth - totalMenuEntriesWidth) / MenuEntries.Count;
                int offset = (viewport != null && viewport.HasValue) ? viewport.Value.X : 0;
                //Start at the margin 
                Vector2 position = new Vector2(offset + menuEntryPadding / 2, ((viewport != null && viewport.HasValue) ? viewport.Value.Y + MenuEntries[0].GetHeight(this) / 2 : defaultY));

                // update each menu entry's location in turn
                foreach (var menuEntry in MenuEntries)
                {
                    if (ScreenState == ScreenState.TransitionOn)
                        position.X -= transitionOffset * 256;
                    else
                        position.X += transitionOffset * 512;

                    menuEntry.Position = position;

                    position.X += menuEntry.GetWidth(this) + menuEntryPadding;
                }
            }
        }


        /// <summary>
        /// Updates the menu.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            // Update each nested MenuEntry object.
            for (int i = 0; i < MenuEntries.Count; i++)
            {
                bool isSelected = IsActive && (i == selectedEntry);

                MenuEntries[i].Update(this, isSelected, gameTime);
            }
        }


        /// <summary>
        /// Draws the menu.
        /// </summary>
        public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            Viewport viewport = Bomberman.Instance.GraphicsDevice.Viewport;

            if (IsPopup)
            {
                int menuItemsHeight = MenuEntries.Count > 0 ? MenuEntries[0].GetHeight(this) : 0;

                // Darken down any other screens that were drawn beneath the popup.
                ScreenManager.FadeBackBufferToBlack(spriteBatch, TransitionAlpha * 2 / 3);

                // Center the message text in the viewport.
                Vector2 viewportSize = new Vector2(viewport.Width, viewport.Height);
                Vector2 textSize = ScreenManager.PopupFont.MeasureString(menuTitle);
                Vector2 textPosition = ((viewportSize - textSize) / 2) - new Vector2(0, menuItemsHeight);

                // The background includes a border somewhat larger than the text itself.
                const int hPad = 32;
                const int vPad = 16;

                Rectangle backgroundRectangle = new Rectangle((int)textPosition.X - hPad,
                                                              (int)textPosition.Y - vPad,
                                                              (int)textSize.X + hPad * 2,
                                                              (int)textSize.Y + menuItemsHeight + vPad * 2);

                var q = new Rectangle(backgroundRectangle.X + hPad,
                                                       (int)(backgroundRectangle.Y + ScreenManager.PopupFont.MeasureString(menuTitle).Y + vPad),
                                                       backgroundRectangle.Width - hPad * 2,
                                                       (int)(backgroundRectangle.Height - (textSize.Y + vPad)));

                // make sure our entries are in the right place before we draw them
                UpdateMenuEntryLocations(q);

                // Fade the popup alpha during transitions.
                var color = Color.White * TransitionAlpha;

                spriteBatch.Begin();

                // Draw the background rectangle.
                spriteBatch.Draw(gradientTexture, backgroundRectangle, color);


                // Draw each menu entry in turn.
                for (int i = 0; i < MenuEntries.Count; i++)
                {
                    MenuEntry menuEntry = MenuEntries[i];
                    bool isSelected = IsActive && (i == selectedEntry);
                    menuEntry.Draw(spriteBatch, this, isSelected, gameTime);
                }

                // Draw the message box text.
                spriteBatch.DrawString(ScreenManager.PopupFont, menuTitle, textPosition, color);

                spriteBatch.End();
            }
            else
            {
                // make sure our entries are in the right place before we draw them
                UpdateMenuEntryLocations();

                spriteBatch.Begin();

                // Draw each menu entry in turn.
                for (int i = 0; i < MenuEntries.Count; i++)
                {
                    MenuEntry menuEntry = MenuEntries[i];
                    bool isSelected = IsActive && (i == selectedEntry);
                    menuEntry.Draw(spriteBatch, this, isSelected, gameTime);
                }

                // Make the menu slide into place during transitions, using a
                // power curve to make things look more interesting (this makes
                // the movement slow down as it nears the end).
                float transitionOffset = (float)Math.Pow(TransitionPosition, 2);

                // Draw the menu title centered on the screen
                Vector2 titlePosition = new Vector2(viewport.Width / 2, 80);
                Vector2 titleOrigin = ScreenManager.TitleFont.MeasureString(menuTitle) / 2;
                Color titleColor = new Color(192, 192, 192) * TransitionAlpha;
                float titleScale = 1.25f;

                titlePosition.Y -= transitionOffset * 100;

                spriteBatch.DrawString(ScreenManager.TitleFont, menuTitle, titlePosition, titleColor, 0,
                                       titleOrigin, titleScale, SpriteEffects.None, 0);

                spriteBatch.End();
            }
        }


        #endregion
    }
}
